package lab2rsagroup10;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class RSA {
	
    public static class Key {
    	public final BigInteger key;
    	public final BigInteger modulos;
    	
    	public Key(final BigInteger key, final BigInteger modulos) {
    		this.key = key;
    		this.modulos = modulos;
    	}
    }
    
    public static class Keys {
    	public final RSA.Key publicKey;
    	public final RSA.Key privateKey;
    	
    	public Keys(final RSA.Key publicKey, final RSA.Key privateKey) {
    		this.publicKey = publicKey;
    		this.privateKey = privateKey;
    	}
    }

    /**
     * Generates 512 bit keys.
     * @return A public/private key tuple.
     */
    public static RSA.Keys generateKeys() {
    	BigInteger p = Util.generatePrime(512);
    	BigInteger q = Util.generatePrime(512);
    	BigInteger n = p.multiply(q);
    	BigInteger phi = p.subtract(BigInteger.ONE).
	    	multiply(q.subtract(BigInteger.ONE));
    	BigInteger log = BigInteger.valueOf(phi.bitLength());
    	
    	BigInteger public_key;
    	Random rand = new Random();
    	do {
    		public_key = new BigInteger(512, rand);
    		public_key = public_key.mod(phi.subtract(log)).add(log);
    	} while(public_key.gcd(phi).compareTo(BigInteger.ONE) != 0);
    	
    	BigInteger private_key = Util.inv(public_key, phi);
    	
    	return new RSA.Keys(new RSA.Key(public_key, n), 
    			new RSA.Key(private_key, n));
    }
    
    /**
     * Encrypts a message. The message has to be shorter than the modulos of the key.
     * @param message - The message to be encrypted
     * @param key - The key used to encrypt the message.
     * @return BigInteger representation of the encrypted message.
     */
    public static BigInteger encrypt(final BigInteger message, final RSA.Key key) {
    	assert message.bitLength() < key.key.bitLength();
    	return Util.modPow(message, key.key, key.modulos);
    }
    
    /**
     * Encrypts a whole string message.
     * @param message - The message to be encrypted.
     * @param key - The key used to encrypt the message.
     * @return A list of the encrypted blocks.
     */
    public static List<BigInteger> encrypt(final String message, final RSA.Key key) {
    	int len = message.length();
    	ArrayList<BigInteger> c = new ArrayList<BigInteger>();
    	
    	if (len == 0) {
    		c.add(BigInteger.ZERO);
    		return c;
    	}
    	
    	boolean congrmod2 = true;
    	if (len % 2 != 0) {
    		congrmod2 = false;
    		len -= 1;
    	}
    	
    	String subm;
    	BigInteger intm;
    	for(int i = 0; i < len; i += 2) {
    		subm = message.substring(i, i+2);
    		intm = new BigInteger(subm.getBytes());
    		c.add(encrypt(intm, key));
    	}
    	
    	if(!congrmod2) {
    		byte[] lastchar = {(byte) message.charAt(len)};
    		c.add(encrypt(new BigInteger(lastchar), key));
    	}
    	
    	return c;
    }
    
    /**
     * Decrypts a cipher.
     * @param cipher - The cipher to be decrypted.
     * @param key - The key.
     * @return A string representation of the decrypted integer cipher.
     */
    public static String decrypt(BigInteger cipher, RSA.Key key) {
    	//encryption is the same operation as decryption
    	return new String(encrypt(cipher, key).toByteArray());
    }

    /**
     * Tries to break an RSA ciphered text.
     * @param parser - The parser to be used.
     * @param r - The number of bits that the cipher text is encoded with.
     * @return The plaintext.
     */
    public static String breakMessage(final AbstractRSAParser parser, final int r) {
    	String message = "";
    	RSABreaker breaker = new RSABreaker(parser.getPublicKey(), r);
    	
    	String block;
    	for(BigInteger cipher : parser.getCipherText()) {
    		block = breaker.findMessage(cipher);
    		if(block != null) {
    			message += block;
    		} else {
    			message += "  ";
    		}
    	}
    	
    	return message;
    }
}